%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Industrial Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% readImageByIndex.m
% readImageFile.m
% readdpx.m
% Image processing toolbox
% findMinRelativeDifference.m
% writeToLogFile.m
%
%==========================================================================

%==========================================================================
% The function "checkSignalOverlapsAgainstImages" takes a sequence of sound
% signals of the same length along with an estimate for the amount of
% overlap for every pair of consecutive signals. The pairs for which the
% estimate differs form a given overlap guess by more than a prescribed
% amount are reevaluated. The new estimates are based on comparing the
% images from whose soundtracks the corresponding sound signals are
% extracted. Before testing the various possible overlaps between two
% images they are calibrated using given sound-based calibration masks.
% INPUT: "allSignalsArr" is a 2D array whose columns hold the individual
% signals whose overlaps are to be computed.
% "leftCut" is the number of samples removed at the beginnings of the
% original signals in the process of computing the given signals.
% "rightCut" is the number of samples removed at the ends of the original 
% signals in the process of computing the given signals.
% "overlapArr" is a column vector holding already computed amounts of
% overlap (measured in number of samples) between every two consecutive
% signals. Its length is one less than the number of all given signals.
% "overlapGuess" specifies a guess for the most likely amount of overlap in
% any pair of consecutive signals. It is measured in number of samples.
% "checkThreshold" is a positive integer specifying a threshold used to
% determine which overlaps need to be reevaluated: if the absolute
% difference between an overlap and the overlap guess is greater than this
% threshold then this overlap is subjected to a new, image-based estimate.
% "imageFileNamePrefix" is a string specifying the common prefix (including
% a full directory path) in the names of all image files in the sequence
% from which the sound signals are extracted.
% "fileIndArr" is a 1D array specify (as integers) the indices of the
% image files from which the individual sound signals have been extracted.
% "imageBasedMaskArr" is an optional 2D image array of the same size as the
% image files to be processed. It specifies a multiplicative calibration
% mask which is to be applied to the images before any other processing.
% "soundBasedMaskArr" is a 2D array of the same size as the input array of
% signals. Its columns hold the individual calibration masks by which the
% corresponding original signals are to be divided in order to produce
% their calibrated versions. Each of these calibration masks can be used to
% calibrate the corresponding image: each column of the image is to be
% divided pointwise by the mask.
% "initSearchRad" is a positive integer specifying the radius of an
% interval centered at the overlap guess where the initial search for the
% best-matching overlap is carried out. It is to reflect the belief of the
% calling function with respect to how far the best-matching overlaps are
% likely to be from the overlap guess.
% "locMinNbhdRad" is a positive integer specifying the radius of the
% interval around a sample where values of a function are checked in order
% to determine if it has a local minimum at this sample. More precisely,
% the function is considered to have a local minimum at the sample if all
% function values in the said interval are not smaller than the value at
% the central sample.
% "indFieldLength" is an optional parameter specifying the number of digits
% used to specify the index of a image in its file name. Image indices are
% specified with leading zeros. For example, if the index field length is
% 6, then the index of image 241 is specified as 000241 in its file name.
% "imageFileExt" is an optional string specifying the extension of the
% image file to be read. Admissible extensions are 'dpx' and 'pgm' with the
% former used by default.
% "waitbarStruct" is an optional parameter specifying a structure of
% parameters used to set a waitbar. Those parameters are a waitbar handle,
% a starting position on the waitbar (a number between 0 and 1), a relative
% length along the waitbar (a number between 0 and (1 - starting position),
% an initial value for a counter that counts the events whose progress is
% being illustrated by the waitbar, and a total reflecting the total number
% of events of the said kind.
% OUTPUT: "overlapArr" returns a column vector holding the amounts of
% overlap (measured in number of samples) between every two consecutive
% signals. Some of those amounts are reevaluated and the others are as in
% the input. The length of the column vector is one less than the number of
% the given signals.
% "minDiffArr" returns a column vector of the same length as the array of
% overlaps "overlapArr". It holds zeros except for the positions where the
% amount of overlap was reevaluated. There it contains the relative average
% absolute difference between the overlapping portions of the corresponding
% consecutive images.
%==========================================================================
function [overlapArr, minDiffArr] = checkSignalOverlapsAgainstImages(allSignalsArr, ...
    leftCut, rightCut, overlapArr, overlapGuess, checkThreshold, ...
    imageFileNamePrefix, fileIndArr, imageBasedMaskArr, soundBasedMaskArr, ...
    initSearchRad, locMinNbhdRad, indFieldLength, imageFileExt, waitbarStruct)

% Get the common length of the signals.
signalLength = size(allSignalsArr, 1);

% Define default values for the optional input parameters.
if (nargin < 11)
    initSearchRad = 10;
end
if (nargin < 12)
    locMinNbhdRad = 5;
end
if (nargin < 13)
    indFieldLength = 6;
end
if (nargin < 14)
    imageFileExt = 'dpx';
end
% Specify that color images are to be processed as grayscale images.
doConvertToGray = true;

% Allocate memory to store the minimal relative differences.
minDiffArr = zeros(size(overlapArr));

% Precompute the range of overlaps to be initially searched for a local
% minimum of the realtive differences. This range will be used repeatedly.
initSearchIntLeftEnd = max(1, overlapGuess - initSearchRad);
initSearchIntRightEnd = min(signalLength, overlapGuess + initSearchRad);

% Precompute the range of overlaps for relative differences are to be
% computed initially. This range will be used repeatedly.
initMinOverlap = max(1, initSearchIntLeftEnd - locMinNbhdRad);
initMaxOverlap = min(signalLength, initSearchIntRightEnd + locMinNbhdRad);

% Find the overlaps which are too far from the overlap guess.
checkIndArr = find(abs(overlapArr - overlapGuess) > checkThreshold);

% Compute the best-matching overlap for all pairs of consecutive signals,
% processing pairs one at a time.
for k = 1:numel(checkIndArr)
    
    % Read in the two images whose overlap is to be computed in an
    % image-based fashion.
    firstImageArr = double(readImageByIndex(fileIndArr(checkIndArr(k)), ...
        imageFileNamePrefix, indFieldLength, imageFileExt, doConvertToGray));
    secondImageArr = double(readImageByIndex(fileIndArr(checkIndArr(k) + 1), ...
        imageFileNamePrefix, indFieldLength, imageFileExt, doConvertToGray));
    
    % Caliblrate the two current images with the image-based mask.
    if (isempty(imageBasedMaskArr) == false)
        firstImageArr = firstImageArr.*imageBasedMaskArr;
        secondImageArr = secondImageArr.*imageBasedMaskArr;
    end
    
    % Reduce the two current images.
    firstImageArr = firstImageArr((leftCut + 1):(end - rightCut), :);
    secondImageArr = secondImageArr((leftCut + 1):(end - rightCut), :);
    
    % Caliblrate the two current images with the sound-based mask.
    numCols = size(firstImageArr, 2);
    firstImageMaskArr = soundBasedMaskArr(:, checkIndArr(k));
    secondImageMaskArr = soundBasedMaskArr(:, checkIndArr(k) + 1);
    for colInd = 1:numCols
        firstImageArr(:, colInd) = firstImageArr(:, colInd)./firstImageMaskArr;
        secondImageArr(:, colInd) = secondImageArr(:, colInd)./secondImageMaskArr;
    end
    
    % Transpose the two current images.
    firstImageArr = firstImageArr.';
    secondImageArr = secondImageArr.';
    
    % Set the current range of overlaps for which relative differences are
    % to be computed.
    minOverlap = initMinOverlap;
    maxOverlap = initMaxOverlap;
    
    % Initialize the variable storing the smallest relative difference to
    % anything that will not be exceeded by the first actually computed
    % smallest relative difference.
    minRelDiff = inf;
    
    % Compute the relative differences over the initial range of overlaps
    % and determine the overlap corresponding to the smallest of those
    % differences.
    [newBestOverlap, newMinRelDiff] = findMinRelativeDifference(firstImageArr, ...
        secondImageArr, minOverlap, maxOverlap);
    
    if (newBestOverlap < initSearchIntLeftEnd)
        % If the best-matching from the initial range of overlaps is found
        % BELOW the initial search interval, then keep extending the search
        % interval to the LEFT and keep computing relative differences for
        % additional amounts of overlap until the best-matching overlap is
        % found inside the (current) search interval.
        
        % Note that this loop is always executed at least once.
        while (newMinRelDiff < minRelDiff)
            
            % Store the found better-matching overlap and the corresponding
            % smallest relative difference.
            bestOverlap = newBestOverlap;
            minRelDiff = newMinRelDiff;
            
            % Determine additional, smaller amounts of overlap for
            % which relative differences need to be computed.
            maxOverlap = max(1, minOverlap - 1);
            minOverlap = max(1, bestOverlap - locMinNbhdRad);
            
            % Compute the relative differences over the current range of
            % overlaps and determine the overlap corresponding to the
            % smallest of those differences.
            [newBestOverlap, newMinRelDiff] = findMinRelativeDifference(firstImageArr, ...
                secondImageArr, minOverlap, maxOverlap);
        end
        
    elseif (newBestOverlap > initSearchIntRightEnd)
        % If the best-matching from the initial range of overlaps is found
        % ABOVE the initial search interval, then keep extending the search
        % interval to the RIGHT and keep computing relative differences for
        % additional amounts of overlap until the best-matching overlap is
        % found inside the (current) search interval.
        
        % Note that this loop is always executed at least once.
        while (newMinRelDiff < minRelDiff)
            
            % Store the found better-matching overlap and the corresponding
            % smallest relative difference.
            bestOverlap = newBestOverlap;
            minRelDiff = newMinRelDiff;
            
            % Determine additional, larger amounts of overlap for which
            % relative differences need to be computed.
            minOverlap = min(signalLength, maxOverlap + 1);
            maxOverlap = min(signalLength, bestOverlap + locMinNbhdRad);
            
            % Compute the relative differences over the current range of
            % overlaps and determine the overlap corresponding to the
            % smallest of those differences.
            [newBestOverlap, newMinRelDiff] = findMinRelativeDifference(firstImageArr, ...
                secondImageArr, minOverlap, maxOverlap);
        end
        
    else
        % If the best-matching from the initial range of overlaps is found
        % INSIDE the initial search interval, then simply store it along
        % with the corresponding smallest relative difference.
        bestOverlap = newBestOverlap;
        minRelDiff = newMinRelDiff;
    end
    
    % Store the best-matching overlap and the corresponding relative
    % difference.
    overlapArr(checkIndArr(k)) = bestOverlap;
    minDiffArr(checkIndArr(k)) = minRelDiff;
    
    
    % Update the progress bar.
    if (exist('waitbarStruct', 'var') == 1)
        if getappdata(waitbarStruct.handle, 'canceling')
            return;
        end
        if (waitbarStruct.total == 0)
            waitbarStruct.total = numel(checkIndArr);
        end
        waitbar(waitbarStruct.start + (waitbarStruct.length)*(k/numel(checkIndArr)), ...
            waitbarStruct.handle, sprintf('Step %d of %d: perfecting overlap %d out of %d', ...
            waitbarStruct.index, waitbarStruct.totalIndices, ...
            waitbarStruct.first + k - 1, waitbarStruct.total));
    end
    
    if (mod(k, 20) == 0)
        fprintf(1, 'Pair %d out of %d is done.\n', k, numel(checkIndArr));
    end
end

% Write info to a log file.
aveOverlap = mean(overlapArr);
writeToLogFile(['average overlap, sound-based image-corrected: ' num2str(aveOverlap + leftCut + rightCut)]);
writeToLogFile(['average overlap, sound-based image-corrected reduced: ' num2str(aveOverlap)]);
writeToLogFile(['standard deviation of overlap, sound-based image-corrected: ' num2str(std(overlapArr, 1))]);
writeToLogFile(['max absolute deviation of overlap, sound-based image-corrected: ' num2str(max(abs(overlapArr - aveOverlap)))]);

return;
% end of the function "checkSignalOverlapsAgainstImages"
